<?php
/* Ballot Class for use with VoteSmart API */

     include_once("../lib/votesmart/VoteSmart.php");



class Ballot
{
     private $zip; //5-digit zipcode
     private $plus4; //4-digit zip+4 part of zipcode
     private $state;
     private $voteDate; //date of the election
     private $electionData; //array of Election objects containing the offices in election and its array of associated candidates
     private $measureData; //array of Measure objects containing ballot measure names and numbers
     private $votesmart;
     private $debug;//for turning on (true) and off (false) XML address echos from votesmart API
     private $noElections;
     private $noMeasures;


     public function __construct($zipcode, $zipplus4=NULL, $date=NULL)
     {
	  $this->zip = $zipcode;
	  $this->plus4 = $zipplus4;
	  $this->voteDate = $date;
	  $this->electionData = array();
	  $this->measureData = array();
	  $this->votesmart = new VoteSmart();
	  $this->debug = false; //for turning on (true) and off (false) XML address echos from votesmart API
	  $this->noElections = false;
	  $this->noMeasures = false;



	 if(!$this->debug) ob_start();//block echos from votesmart api
	  //look for elections with the given zipcode
	 $this->votesmart->query('Election.getElectionByZip', array( 'zip5' => $this->zip, 'zip4' => $this->plus4));
	  // Get the SimpleXML object
	 $basicData = $this->votesmart->getXmlObj();

	  //look for ballot measures for this state
	  //cannot get by zip so have to get user's state and curr year
	  //use district lookup to get state, just in case no elections only ballot measure (in that case above query won't give state)
	  $this->votesmart->query('District.getByZip', array( 'zip5' => $this->zip, 'zip4' => $this->plus4) );
	  $distData = $this->votesmart->getXmlObj();


	  if(!$this->debug) ob_end_clean();
	  //check for need of zip+4 info, if so stop
	 if((strlen($basicData->zipMessage) > 0) || (strlen($distData->zipMessage) > 0)) {//need plus4, category is there sometimes but no message in it
	       throw new NeedPlusFourException();
	 }
	  if(!$this->debug) ob_start();//block echos from votesmart api
	 $this->state = $distData->district[0]->stateId;

	 //get any ballot measures
	  $this->votesmart->query('Measure.getMeasuresByYearState', array( 'year' => date("Y"), 'stateId' => $distData->district[0]->stateId));
	  // Get the SimpleXML object
	  $measureData = $this->votesmart->getXmlObj();
	  //stop blocking echos
	  if(!$this->debug) ob_end_clean();

	  //set error check booleans
	 if(isset($basicData->errorMessage)) $this->noElections = true;
	 if(isset($measureData->errorMessage)) $this->noMeasures = true;

	  if ( $this->noElections && $this->noMeasures ) { // If neither there's no ballot
	       throw new NoBallotException();
	 }
	 //otherwise get more information to fill up the ballot and check dates
	 //VoteSmart "Election" objects are the individual groups of offices in contention for this election, ie Presidential, Congressional...
	 //get the election numbers from VoteSmart

	  $today =  time();
	  //*******************fake today to get elections for testing*******************************
	  //$today = strtotime("05/01/2009");
	 if(!$this->noElections){//possible there is no candidates, only ballot measures
	  foreach ($basicData->election as $election) {
		    $tempElection = new Election();
		    //query VoteSmart for each office running for election on this ballot
		    $electionId = $election->electionId;
		    if(!$this->debug)ob_start();
		    $this->votesmart->query('Election.getElection', array( 'electionId' => $electionId));
		    $elecData = $this->votesmart->getXmlObj();
		    if(!$this->debug) ob_end_clean();
		    //now have get the right votesmart "stage" (Primary, General,...) for the current date
		    //go through stages of this election and get the soonest one in the future

		    $tempElection->voteDate = "01/18/2038";//dummy first one = unix max date
		    foreach ($elecData->election->stage as $stage) {
			 $stageDate = strtotime($stage->electionDate);
			 if($stageDate >= $today && $stageDate <= strtotime($tempElection->voteDate)){//this election is in the future
			      $tempElection->electionId = $electionId;
			      $tempElection->electionName =  $elecData->election->name;
			      $tempElection->voteDate = $stage->electionDate;
			      $tempElection->stageName = $stage->name;
			      $tempElection->stageId = $stage->stageId;
			      $this->electionData[] = $tempElection;
			 }
		    }
	       }
	  }

	  //get ballot measure list
	  if(!$this->noMeasures)
	  {
	       foreach($measureData->measure as $m)
	       {
		   if(!$this->debug)ob_start();
		    //have to do this to check vote date the same
		    $this->votesmart->query('Measure.getMeasure', array ('measureId' => $m->measureId));
		    $measData = $this->votesmart->getXmlObj();
		    //stop blocking echos
		    if(!$this->debug) ob_end_clean();

		    $voteDate = strtotime($measData->electionDate);
		    if($voteDate >= $today){//this election is in the future
			 $tempMeasure = new Measure($m->measureId, $m->measureCode, $m->title, $measData->electionDate);
			 $this->measureData[] = $tempMeasure;
		    }
	       }

	  }
	  //now have to check that all saved vote dates are the same day
	  $this->checkVoteDates();

	  //make sure list of elections and measures both still not empty!
	  if(count($this->electionData) == 0) $this->noElections = true;
	  if(count($this->measureData) == 0) $this->noMeasures = true;

	  //If it is all are past but are still in the XML because they were this year
	  // throw exception
	  if($this->noElections && $this->noMeasures){
	      	 throw new NoBallotException();

	  }

	  if(!$this->noElections){
	       //get the real office names for pretty printout
	       $officeNames = $this->getOfficeNames();

	       //make array from previous distData: voteSmart officeID -> state District number
	       foreach($distData->district as $dist){
		    if((int)$dist->name != 0){ //some weird non-numeric ones seem wrong here
			 $key = (int)$dist->officeId;
			 $distOfcArray[$key] = (int)$dist->name;
		    }
	       }
	       //get and save only the valid candidates for stored Elections
	       $this->fillBallotWithCandidates($distOfcArray, $officeNames);
	  }

     }

     public function __get($name)
     {
	  return $this->$name;
     }

     public function __set($attribute, $value)
     {
	  $this->$attribute = $value;
     }

     /*Returns Ballot object information as a multi-dimensional array */
     /* In case Ballot object needs to be kept as a session variable */
     public function getBallot()
     {
	  $ballotArray = array();
	  $ballotArray['zip'] = $this->zip;
	  $ballotArray['plus4'] = $this->plus4;
	  $ballotArray['state'] =  $this->state;
	  $ballotArray['date'] = $this->voteDate;
	  $ballotArray['elections'] = array();
	   foreach($this->electionData as $e){
	       $electArray = array();
	       $electArray['name'] = (string)$e->electionName;
	       $electArray['stage'] = (string)$e->stageName;
	       $electArray['offices'] = array();

	     foreach($e->offices as $ofc => $cand){
	       $ofcArray = array();
	       $ofcArray['Office'] = (string)$ofc;
	       $ofcArray['candidates'] = array();
	       foreach($cand as $c){
		    $candArray = array();
		    $candArray['firstName'] = $c->firstName;
		    $candArray['middleName'] = $c->middleName;
		    $candArray['nickName'] = $c->nickName;
		    $candArray['lastName'] = $c->lastName;
		    $candArray['party'] = $c->party;
		    //put this array into candidate section of office array
		    $ofcArray['candidates'][] = $candArray;
	       }
	       $electArray['offices'][] = $ofcArray;
	       }
	       $ballotArray['elections'][] = $electArray;
	  }//done adding elections
	  $ballotArray['measures'] = array();

	  foreach($this->measureData as $m)
	  {
	       $measArray = array();
	       $measArray['code'] =  $m->measureCode;
	       $measArray['title'] =  $m->measureTitle;

	       $ballotArray['measures'][] = $measArray;
	  }
	 return $ballotArray;
     }


     public function printBallot()
     {
	 echo '<ul>';
	 foreach($this->electionData as $e){
	      echo '<li><h2>'. $e->electionName .' '. $e->stageName . ' Election.</h2></li>';
	      echo '<ul>';

	     foreach($e->offices as $ofc => $cand){
	       echo '<li><h3>Office: '.$ofc .'</h3></li>';
	       echo '<ul>';
	       foreach($cand as $c){
		    echo '<li>';
		    $c->showCandidate();
		    echo '</li>';
	       }
	       echo '</ul>';
	       }
	       echo '</ul>';
	  }
	  foreach($this->measureData as $m)
	  {
	       echo '<li><h2>'. $m->measureCode .'</h2></li>';
	       echo '<ul>';
	       echo '<li>'. $m->measureTitle .'</li></ul>';
	  }
	 echo '</ul>';

     }

/* ********************* PRIVATE FUNCTIONS *********************************/
     //get valid candidates for the offices in the stage/elections saved
     //store them in the Election objects stored in Ballot->electionData
     private function fillBallotWithCandidates($dData, $oNames)
     {

	 foreach($this->electionData as $elect)
	 {
	      $tempArray = array();
	      if(!$this->debug) ob_start();
	      $this->votesmart->query('Election.getStageCandidates', array( 'electionId' => $elect->electionId, 'stageId' => $elect->stageId ));
	      $stageData =  $this->votesmart->getXmlObj();
	      if(!$this->debug) ob_end_clean();

	      foreach($stageData->candidate as $cand){
		    //check to make sure this candidate is in the right districts
		    if($dData[(int)$cand->officeId] == (int)$cand->district){
			 //convert votesmart officeId number to office name
			 //have to do some casting to make it work between votesmart objects
			 $key = (int)$cand->officeId;
			 $ofcName = (string)$oNames[$key];
			 if(strlen($cand->district) > 0){ //there is district number(s) associated with this office add to the name
			      $ofcName = $ofcName. ' District '. (string)$cand->district;
			  }
			 $tempCandidate = new Candidate($cand->firstName, $cand->middleName, $cand->nickName, $cand->lastName, $cand->party);

		    //add this candidate to the office->candidates array
			 $tempArray[$ofcName][] = $tempCandidate;
		    }
		 }
	         $elect->offices = $tempArray;
	 }

     }

     //Removes all elections except for the soonest ones on the same date
     //only does anything if there are elections in the future with different
     //soonest voting dates
     //Sets the Ballot object voteDate to soonest future date
     private function checkVoteDates()
     {
	  $multiDates=false;
	  if(!$this->noElections){
	       $minDate = strtotime($this->electionData[0]->voteDate);
	       foreach($this->electionData as  $ed)
	       {
		    $date = strtotime($ed->voteDate);
		    if($minDate != $date){
			 $multiDates = true;
			 if($minDate > $date){
			      $minDate = $date;
			 }
		    }
	       }
	  }else{
	       $minDate = strtotime($this->measureData[0]->voteDate);
	  }
	  if(!$this->noMeasures){
	       foreach($this->measureData as $md)
	       {
		    $date = strtotime($md->voteDate);
		    if($minDate != $date){
			 $multiDates = true;
			 if($minDate > $date){
			      $minDate = $date;
			 }
		    }
	       }
	  }
	  if($multiDates)
	  {//remove all elections but soonest
	       	  foreach($this->electionData as $i => $ed)
		  {
		       $date = strtotime($ed->voteDate);
		       if($date > $minDate){
			      //take out the ones that are further in the future
			      unset($this->electionData[$i]);
			}

		  }
		 foreach($this->measureData as $i => $md)
		  {
		       $date = strtotime($md->voteDate);
		       if($date > $minDate){
			      //take out the ones that are further in the future
			      unset($this->measureData[$i]);
			}

		  }
	  }
	  $this->voteDate = date("D M j Y", $minDate);
     }


     private function getOfficeNames()
     {
	  $tempArray = array();
	  //first have to get VoteSmart office types, "Congressional, Presidential, ..."
	  if(!$this->debug) ob_start();//block echos from votesmart api
	  $this->votesmart->query('Office.getTypes', array());
	  $typedata = $this->votesmart->getXmlObj();
	  foreach($typedata->type as $otype)
	  {
	       $this->votesmart->query('Office.getOfficesByType', array('typeId' =>  $otype->officeTypeId));
	       $ofcdata = $this->votesmart->getXmlObj();
	       foreach($ofcdata->office as $ofc)
	       {
		    $key = (int)$ofc->officeId;
		    $ofcname = $ofc->name;
		    $tempArray[$key] = $ofcname;
	       }

	  }
	  if(!$this->debug) ob_end_clean();
	  return $tempArray;
     }


     function _destruct()
     {
     }




}//close class

class NoBallotException extends Exception
{
     function _toString()
     {
	  return 'noBallotException '. $this->getCode(). ': '. $this->getMessage().'<br /> in '. $this->getFile(). ' on line '.$this->getLine(). '<br />';
     }
}

class NeedPlusFourException extends Exception
{
     function _toString()
     {
	  return 'needPlusFourException'. $this->getCode(). ': '. $this->getMessage().'<br /> in '. $this->getFile(). ' on line '.$this->getLine(). '<br />';
     }
}

class Election
{
     private $electionId; //VoteSmart id no for this election
     private $electionName;//Heading name ie: California State Judicial 2008
     private $stageName; //ie: Primary, General
     private $electionType; //ie: President,
     private $voteDate; //vote date for this stage
     private $district;
     private $offices;//associative array: office name => array of candidate objects

     public function __construct()
     {
	  $this->electionId = NULL;
	  $this->electionName = NULL;
	  $this->stageName = NULL;
	  $this->electionType = NULL;
	  $this->state = NULL;
	  $this->voteDate = NULL;
	  $this->district = NULL;
	  $this->offices = array();
     }

     public function __get($name)
     {
	  return $this->$name;
     }

     public function __set($attribute, $value)
     {
	  $this->$attribute = $value;
     }

     public function setOffice($name, $value)
     {
	   $this->offices[$name] = $value;
     }
     public function getOffice($name)
     {
	  if (array_key_exists($name, $this->offices)) {
            return $this->offices[$name];
	  }
     }
     public function __isset($name) {
        return isset($this->offices[$name]);
    }
}


class Candidate
{
     private $firstName;
     private $middleName;
     private $nickName;
     private $lastName;
     private $party;


     public function __construct($first=NULL, $middle=NULL, $nick=NULL, $last=NULL, $pty=NULL)
     {
	  $this->firstName = $first;
	  $this->middleName = $middle;
	  $this->nickName = $nick;
	  $this->lastName = $last;
	  $this->party = $pty;
     }

     public function __get($name)
     {
	  return $this->$name;
     }

     public function __set($attribute, $value)
     {
	  $this->$attribute = $value;
     }
     public function showCandidate()
     {
	  $nameString =  $this->firstName. ' ' .$this->middleName. ' ';
	  if(strlen($this->nickName) > 0){ $nameString = $nameString. '"'.$this->nickName.'" ';}
	  $nameString = $nameString. $this->lastName;

	  echo '<p>Name: '.$nameString. '</p>';
	  echo '<p>Party: '.$this->party .'</p>';
     }

}

class Measure
{
     private $measureId; //VoteSmart id no for this measure
     private $measureCode;//Proposition 13
     private $measureTitle; //ie:Education Funding Payment Plan
     private $voteDate; //vote date for this stage

     public function __construct($id = NULL, $code = NULL, $title = NULL, $vdate = NULL)
     {
	  $this->measureId = $id;
	  $this->measureCode = $code;
	  $this->measureTitle = $title;
	  $this->voteDate = $vdate;
     }

     public function __get($name)
     {
	  return $this->$name;
     }

     public function __set($attribute, $value)
     {
	  $this->$attribute = $value;
     }

     public function showMeasure()
     {
	  echo '<p>' .$this->measureCode. '  ' . $this->measureTitle .'</p>';
     }
}

?>
